import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import javax.swing.table.DefaultTableModel;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.paukov.combinatorics.Factory;
import org.paukov.combinatorics.Generator;
import org.paukov.combinatorics.ICombinatoricsVector;

public class Table {
	private String type;
	private ArrayList<Points> table;
	private ArrayList<Integer> listX;
	private ArrayList<Samples> samples;
	private Map<Double, Integer> frequency;

	// getting the mean
	public double getMean() {
		double average = 0;
		for (int i = 0; i < listX.size(); i++) {
			average += listX.get(i);
		}
		return average / listX.size();
	}

	// getting the variance
	public double getVariance() {
		double mean = getMean();
		double variance = 0;
		for (int i = 0; i < listX.size(); i++) {
			double a = listX.get(i) - mean;
			variance += Math.pow(a, 2);
		}

		return variance / listX.size();
	}

	// different types of distribution
	public void generateUniformDistribution(int populationSize, int lowerBound,
			int upperBound) {
		table = new ArrayList<>();
		listX = new ArrayList<>();
		type = "Uniform Distribution";
		Random rand = new Random();
		System.out.println(populationSize);
		int random;
		int frequency;
		do {
			random = rand.nextInt(populationSize) + 2;
			frequency = populationSize / random;
		} while (populationSize % random != 0);
		for (int i = 0; i < populationSize / frequency; i++) {
			random = rand.nextInt((upperBound - lowerBound)) + lowerBound;
			while (listX.contains(random)) {
				random = rand.nextInt((upperBound - lowerBound)) + lowerBound;
			}
			listX.add(random);
		}
		Collections.sort(listX);

		for (int i = 0; i < populationSize / frequency; i++) {
			Points point = new Points(listX.get(i),
					(frequency * 1.0 / populationSize));
			table.add(point);
		}
	}

	private Points isAdded(Points p) {
		for (int i = 0; i < table.size(); i++) {
			Points point = table.get(i);
			if (Points.equalsX(p, point))
				return point;
		}
		return null;
	}

	public void generateRandomDistribution(int populationSize, int lowerBound,
			int upperBound) {
		table = new ArrayList<>();
		listX = new ArrayList<>();

		type = "Random Distribution";
		Random rand = new Random();
		for (int i = 0; i < populationSize; i++) {
			listX.add(rand.nextInt((upperBound - lowerBound)) + lowerBound);
		}
		Collections.sort(listX);
		for (int i = 0; i < listX.size(); i++) {
			if (i == 0) {
					Points point = new Points(listX.get(i), (Collections.frequency(
						listX, listX.get(i))) / (double) populationSize);
					Points compare = isAdded(point);
					if (compare == null) {
						table.add(point);
					} else {
						table.add(compare);
					}
			}

			if (i != 0 && listX.get(i) != listX.get(i - 1)) {
					Points point = new Points(listX.get(i), (Collections.frequency(
						listX, listX.get(i))) / (double) populationSize);
					Points compare = isAdded(point);
					if (compare == null) {
						table.add(point);
					} else {
						table.add(compare);
					}
			}
		}
	}

	public double normalDistribution(double x, double variance, double mean) {
		double a = 1.0 / ((Math.sqrt(2 * Math.PI * variance)));
		double b = 2 * variance;
		double exp = -Math.pow((x - mean), 2) / b;
		double result = a * Math.pow(Math.E, exp);
		return result;
	}

	public void generateNormalDistribution(int populationSize, int lowerBound,
			int upperBound) {
		table = new ArrayList<>();
		listX = new ArrayList<>();
		Random rand = new Random();

		for (int i = 0; i < populationSize; i++) {
			listX.add(rand.nextInt((upperBound - lowerBound)) + lowerBound);
		}

		type = "Normal Distribution";
		Collections.sort(listX);
		
		for (int i = 0; i < listX.size(); i++) {
				double result = normalDistribution((double) listX.get(i),
					getVariance(), getMean());
				Points point = new Points(listX.get(i), result);
				table.add(point);
		}
	}

	public double skewedDistribution(double x, int E, int w, double a) {
		Double result = (2.0 / w) * densityFunction((x - E) / w)
				* (0.5 * (1 + erf(a * ((x - E) / w))));

		return result;
	}

	public double densityFunction(double x) {
		Double result = (1 / Math.sqrt(2 * Math.PI))
				* Math.pow(Math.E, -(Math.pow(x, 2) / 2));

		return result;
	}

	public double erf(double z) {
		double t = 1.0 / (1.0 + 0.5 * Math.abs(z));
		double ans = 1
				- t
				* Math.exp(-z
						* z
						- 1.26551223
						+ t
						* (1.00002368 + t
								* (0.37409196 + t
										* (0.09678418 + t
												* (-0.18628806 + t
														* (0.27886807 + t
																* (-1.13520398 + t
																		* (1.48851587 + t
																				* (-0.82215223 + t * (0.17087277))))))))));
		if (z >= 0)
			return ans;
		else
			return -ans;
	}

	public void generateSkewedDistribution(int populationSize, int lowerBound,
			int upperBound) {
		table = new ArrayList<>();
		listX = new ArrayList<>();
		Random rand = new Random();

		int E = rand.nextInt(upperBound - lowerBound) + lowerBound;
		int w = rand.nextInt(upperBound - lowerBound) + lowerBound;
		Double a;
		
		do{
			a = rand.nextDouble();
		}while(a == 0.0);

		if (rand.nextInt(2) == 0) {
			a = a * -1.0;
		}

		if (rand.nextInt(2) == 0) {
			E = E * -1;
		}

		for (int i = 0; i < populationSize; i++) {
			listX.add(rand.nextInt((upperBound - lowerBound)) + lowerBound);
		}

		type = "Skewed Distribution";
		Collections.sort(listX);

		for (int i = 0; i < listX.size(); i++) {
				double result = skewedDistribution((double) listX.get(i), E, w, a);
				Points point = new Points(listX.get(i), result);
				table.add(point);
		}
	}

	public void generateBimodalDistribution(int populationSize, int lowerBound, int upperBound) {
		table = new ArrayList<>();
		listX = new ArrayList<>();
		Random rand = new Random();
		Double mean1 = 0.0;
		Double mean2 = 0.0;

		for (int i = 0; i < populationSize; i++) {
			listX.add(rand.nextInt((upperBound - lowerBound)) + lowerBound);
		}

		type = "Bimodal Distribution";
		Collections.sort(listX);

		int k = 0;
		while (listX.get(k) < getMean()) {
			mean1 += listX.get(k);
			k++;
		}
		while (k < listX.size()) {
			mean2 += listX.get(k);
			k++;
		}

		mean1 /= (listX.size() / 2);
		mean2 /= (listX.size() / 2);

		for (int i = 0; i < listX.size() / 2; i++) {
				double result = normalDistribution((double) listX.get(i),
					getVariance(), mean1);
				Points point = new Points(listX.get(i), result);
				table.add(point);
		}

		for (int i = (listX.size() / 2) + 1; i < listX.size(); i++) {
				double result = normalDistribution((double) listX.get(i),
					getVariance(), mean2);
				Points point = new Points(listX.get(i), result);
				table.add(point);
		}
	}

	public DefaultTableModel generateModel() {
		Object[] header = { "X", "Y" };
		Object[][] data = new Object[table.size()][2];

		for (int i = 0; i < table.size(); i++) {
			data[i][0] = table.get(i).getX();
			data[i][1] = table.get(i).getY();
		}

		return new DefaultTableModel(data, header);

	}


	// graphing the distribution
	public ChartPanel generateChart() {
		XYSeries xy = new XYSeries(type);
		for (int i = 0; i < table.size(); i++) {
			Points p = table.get(i);
			xy.add(p.getX(), p.getY());
		}
		XYSeriesCollection dataset = new XYSeriesCollection();
		dataset.addSeries(xy);
		JFreeChart chart = ChartFactory.createXYLineChart(type, "X", "Y",
				dataset, PlotOrientation.VERTICAL, true, true, false);
		ChartPanel panel = new ChartPanel(chart);
		return panel;

	}
	
	
	public void generateSamplingDistribution(int depth){
			frequency = new HashMap<Double,Integer>();
			samples = new ArrayList<>();
		 // Create the initial vector of (apple, orange)
		   ICombinatoricsVector<Object> initialVector = Factory.createVector(
		      table.toArray());

		   // Create a multi-combination generator to generate 3-combinations of
		   // the initial vector
		   Generator<Object> gen = Factory.createPermutationWithRepetitionGenerator(initialVector, depth);

		   // Print all possible combinations
		   for (ICombinatoricsVector<Object> combination : gen) {
			  Samples sample = new Samples();
			  for(int i=0; i < combination.getSize(); i++){
				  Points points = (Points) combination.getValue(i);
				  sample.addPoint(points);
			  }
			 if(frequency.get(sample.getMean()) == null)
				 frequency.put(sample.getMean(), 1);
			 else {
				 frequency.put(sample.getMean(), frequency.get(sample.getMean())+1);
			 }
			 
			 samples.add(sample);
			 
		   }
		   
		  generateFx();
	}
	public void generateFx(){
		
			System.out.println("Size of samples: " + samples.size());
			for(int i = 0; i<samples.size(); i++){
				Samples sample = samples.get(i);
				sample.setFx(1.0 * frequency.get(sample.getMean()) / samples.size());
				samples.set(i, sample);
			}
	
	}
	
	public DefaultTableModel generateSampleModel() {
		Object[] header = { "X", "Y" };
		Object[][] data = new Object[samples.size()][2];

		for (int i = 0; i < samples.size(); i++) {
			data[i][0] = samples.get(i).getMean();
			data[i][1] = samples.get(i).getFx();
		}

		return new DefaultTableModel(data, header);

	}
	
	public ChartPanel generateSamplingChart() {
		XYSeries xy = new XYSeries(type);
		for (int i = 0; i < samples.size(); i++) {
			Samples p = samples.get(i);
			xy.add(p.getMean(), p.getFx());
		}
		XYSeriesCollection dataset = new XYSeriesCollection();
		dataset.addSeries(xy);
		JFreeChart chart = ChartFactory.createXYLineChart(type, "X", "Y",
				dataset, PlotOrientation.VERTICAL, true, true, false);
		ChartPanel panel = new ChartPanel(chart);
		return panel;

	}

}